﻿using System;

namespace FirstFloor.Documents.Pdf.Functions
{
    /// <summary>
    /// Represents a PDF Type 0 Sampled function.
    /// </summary>
    internal class SampledFunction
        : Function
    {
        private int[] size;
        private int bitsPerSample;
        private int order;
        private double[] encode;
        private double[] decode;
        private int[] samples;

        /// <summary>
        /// Initializes a new instance of the <see cref="SampledFunction"/> class.
        /// </summary>
        /// <param name="stream">The stream.</param>
        public SampledFunction(StreamObject stream)
            : base(stream.Dictionary)
        {
            this.size = stream.Dictionary.Get<ArrayObject>("Size").ToArray<int>();
            this.bitsPerSample = stream.Dictionary.Get<int>("BitsPerSample");
            this.order = stream.Dictionary.Get<int>("Order", 1);

            ArrayObject encode;
            if (stream.Dictionary.TryGet<ArrayObject>("Encode", out encode)) {
                this.encode = encode.ToArray<double>();
            }
            else {
                this.encode = new double[this.M * 2];
                for (int i = 0; i < this.M; i++) {
                    this.encode[2 * i] = 0;
                    this.encode[2 * i + 1] = this.size[i] - 1;
                }
            }

            ArrayObject decode;
            if (stream.Dictionary.TryGet<ArrayObject>("Decode", out decode)) {
                this.decode = decode.ToArray<double>();
            }
            else {
                this.decode = this.Range;
            }

            var sampleCount = 0;
            for (int i = 0; i < this.M; i++) {
                sampleCount += (this.size[i] * this.N);
            }
            this.samples = new int[sampleCount];

            using (var filteredStream = stream.OpenFilteredStream()) {
                for (int i = 0; i < sampleCount; i++) {
                    if (this.bitsPerSample == 1) {
                        throw new NotImplementedException();
                    }
                    else if (this.bitsPerSample == 2) {
                        throw new NotImplementedException();
                    }
                    else if (this.bitsPerSample == 4) {
                        throw new NotImplementedException();
                    }
                    else if (this.bitsPerSample == 8) {
                        this.samples[i] = filteredStream.ReadByte();
                    }
                    else if (this.bitsPerSample == 12) {
                        throw new NotImplementedException();
                    }
                    else if (this.bitsPerSample == 16) {
                        int sample = filteredStream.ReadByte();
                        sample = sample << 8 + filteredStream.ReadByte();
                        this.samples[i] = sample;
                    }
                    else if (this.bitsPerSample == 24) {
                        int sample = filteredStream.ReadByte();
                        sample = sample << 8 + filteredStream.ReadByte();
                        sample = sample << 8 + filteredStream.ReadByte();

                        this.samples[i] = sample;
                    }
                    else if (this.bitsPerSample == 32) {
                        int sample = filteredStream.ReadByte();
                        sample = sample << 8 + filteredStream.ReadByte();
                        sample = sample << 8 + filteredStream.ReadByte();
                        sample = sample << 8 + filteredStream.ReadByte();

                        this.samples[i] = sample;
                    }
                    else {
                        throw new NotSupportedException(string.Format(Resources.UnsupportedBitsPerSample, this.bitsPerSample));
                    }

                    
                }
            };
        }

        /// <summary>
        /// Provides the actual implementation of the function.
        /// </summary>
        /// <param name="args">The args.</param>
        /// <returns></returns>
        protected override double[] OnExecute(params double[] args)
        {
            //FIXME: only works when M == 1

            var output = new double[this.N];

            for (int i = 0; i < this.M; i++) {
                // encode
                var e = Interpolate(args[0], this.Domain[2*i], this.Domain[2*i+1], this.encode[2*i], this.encode[2*i+1]);
                // clip
                e = Math.Min(Math.Max(e, 0), this.size[i] - 1);

                for (int j = 0; j < this.N; j++) {
                    var r = this.samples[(int)e * this.N + j];

                    // decode
                    output[j] = Interpolate(r, 0, Math.Pow(2, this.bitsPerSample) - 1, this.decode[2 * j], this.decode[2 * j + 1]);
                }
            }

            return output;
        }

        private double Interpolate(double x, double xMin, double Xmax, double yMin, double yMax)
        {
            return yMin + ((x - xMin) * (yMax - yMin) / (Xmax - xMin));
        }
    }
}
